<template>
  <div class="datatable-container" v-bind:class="containerClass">
    <simple-search v-if="searchable" :delaySearch="delaySearch" v-model="search" v-bind:types="searchTypes"></simple-search>
    <loading v-if="loading" :refresh="loadData" class="loading" :error="loadingError" />
    <div class="datatable">
      <table class="table is-bordered is-striped is-narrow" v-bind:class="tableClass">
        <thead>
          <tr>
            <th :class="getThClass(def,index)" @click="setOrderBy(def,index)" v-if="def.visiable" v-for="(def,index) in columnDefs">
              {{def.title}}
              <i v-if="def.searchable" class="fa fa-search" aria-hidden="true"></i>
            </th>
          </tr>
        </thead>
        <tfoot>
        </tfoot>
        <tbody>
          <tr v-for="r in rows" v-bind:class="r.classes">
            <td v-if="v.def.visiable" v-for="v in r" v-bind:class="v.classes">
              <span v-if="v.def.componentable">
              <component v-bind:is="v.component">
              </component>
            </span>
              <span v-else-if="v.def.rawHtml" v-html="v.text"></span>
              <span v-else> {{ v.text }}</span>
            </td>
          </tr>
        </tbody>
      </table>
      <div class="table_info">
        <span v-if="recordsFiltered!=null">
          共 {{recordsFiltered}} 条符合条件,
        </span> 显示第 {{offset}} 至 {{this.offset + this.rowData.length}} 项结果
        <span v-if="recordsTotal !== null">共 {{recordsTotal}} 条记录</span>
      </div>
      <div class="paginate">
        <numbers-pagination v-if="pagingType === 'numbers'" :paginationDot="paginationDot" :setPage="setPage" :pagination="pagination">
        </numbers-pagination>
        <simple-pagination v-if="pagingType === 'simple'" :setPage="setPage" :pagination="pagination">
        </simple-pagination>
      </div>
      <div class="clear"></div>
    </div>
  </div>
</template>
<script>
import moment from 'moment'
import Loading from './Loading'
import _ from 'lodash'
import {
  Simple as SimpleSearch
} from './searches'
import {
  Numbers as NumbersPagination,
  Simple as SimplePagination
} from './paginations'
export default {
  components: {
    NumbersPagination,
    SimplePagination,
    Loading,
    SimpleSearch
  },
  props: {
    searchable: {
      type: Boolean,
      default: true
    },
    searchTypes: {
      type: Array,
      default: () => [{
        type: 'contains',
        label: '包含'
      }]
    },
    delaySearch: {
      type: Number,
      default: 500
    },
    pagingType: {
      type: [String],
      default: 'numbers',
      validator: function (value) {
        return ['numbers', 'simple'].indexOf(value) !== -1
      }
    },
    showTotal: {
      type: [String],
      default: 'always',
      validator: function (value) {
        return ['first', 'never', 'always'].indexOf(value) !== -1
      }
    },
    containerClass: [String, Array, Function],
    tableClass: [String, Array, Function],
    trClass: [String, Array, Function],
    paginationClass: [String, Array, Function],
    initLimit: {
      type: Number,
      default: 20,
      validator: function (value) {
        return value > 0
      }
    },
    delayLoadingTimeout: {
      type: Number,
      default: 600,
      validator: function (value) {
        return value >= 200
      }
    },
    initOffset: {
      type: Number,
      default: 0,
      validator: function (value) {
        return value >= 0
      }
    },
    columns: {
      type: Array,
      require: true
    },
    columnFilter: {
      type: Array,
      default: () => []
    },
    initData: {
      type: [Array, Function, Object],
      validator: function (v) {
        return Array.isArray(v) || typeof (v) === 'function' || v.then
      }
    },
    initOrderBy: {
      type: Array
    },
    paginationDot: {
      type: String,
      default: '...'
    }
  },
  watch: {
    'filteredData': function () {
      this.offset = 0
    }
  },
  created: function () {
    this.loadData()
  },
  data: function () {
    return {
      search: {
        type: 'contains',
        value: ''
      },
      loadingError: null,
      data: [],
      offset: this.initOffset,
      limit: this.initLimit,
      orderBy: this.initOrderBy ? this.initOrderBy : [],
      loading: false
    }
  },
  methods: {
    setOrderBy: function (def, defIndex) {
      if (!def.orderable) {
        return
      }
      let order = [defIndex, 'asc']
      if (this.orderBy.length) {
        if (this.orderBy[0][0] === defIndex && this.orderBy[0][1] === 'asc') {
          order[1] = 'desc'
        }
      }
      this.orderBy = [
        order
      ]
    },
    getThClass: function (def, defIndex) {
      let classes = []
      if (def.searchable) {
        classes.push('searchable')
      }
      if (def.orderable) {
        classes.push('orderable')
        let t = 'both'
        this.orderBy.forEach((o) => {
          if (o[0] === defIndex) {
            t = o[1]
          }
        })
        classes.push(t)
      }
      return classes
    },
    setPage: function (page) {
      this.offset = (page - 1) * this.limit
    },
    loadData: function () {
      this.$emit('loading', this)
      if (Array.isArray(this.initData)) {
        this.data = this.initData
        this.$emit('loaded', this.rowData, this)
        if (this.columns === null) {
          this.columns = Object.keys(this.data)
        }
      } else {
        let delayLoading = setTimeout(() => {
          this.loading = true
        }, this.delayLoadingTimeout)
        if (typeof (this.initData) === 'function') {
          try {
            let data = this.initData()
            if (!Array.isArray(data) && data.then) {
              return data.then((d) => {
                this.loading = false
                clearTimeout(delayLoading)
                delayLoading = null
                this.data = d
                if (this.columns === null) {
                  this.columns = Object.keys(this.data)
                }
                this.$emit('loaded', this.rowData, this)
              }).catch((e) => {
                this.loadingError = e.toString()
              })
            } else {
              this.data = data
              this.loading = false
              clearTimeout(delayLoading)
              delayLoading = null
              this.$emit('loaded', this.rowData, this)
            }
          } catch (e) {
            this.loadingError = e.toString()
          }
        } else {
          this.initData.then((d) => {
            this.loading = false
            clearTimeout(delayLoading)
            delayLoading = null
            this.data = d
            if (this.columns === null) {
              this.columns = Object.keys(this.data)
            }
            this.$emit('loaded', this.rowData, this)
          }).catch((e) => {
            this.loadingError = e.toString()
          })
        }
      }
    }
  },
  computed: {
    filteredData: function () {
      let filters = this.columnFilter.filter(o => o.value !== undefined)
      return this.data.filter((row) => {
        if (filters.length) {
          for (let index in filters) {
            let f = filters[index]
            let v = row[f.name]
            if (f.searchType === 'equal' && f.value !== v) {
              return false
            } else if (f.searchType === 'regex' && !f.value.test(v)) {
              return false
            } else if (f.searchType === 'contains' && v.indexOf(f.value) === -1) {
              return false
            } else if (f.searchType === 'prefix' && v.indexOf(f.value) !== 0) {
              return false
            } else if (f.searchType === 'suffix' && v.slice(0 - f.value.length, v.length) !== f.value) {
              return false
            } else if (['year', 'month', 'week', 'isoWeek', 'quarter'].indexOf(f.searchType) > -1) {
              let vv = moment(v)[f.searchType]()
              if (f.searchType === 'month') {
                vv += 1
              }
              if (vv !== f.value) {
                return false
              }
            }
          }
        }
        if (this.searchable) {
          if (this.search.value) {
            let columns = this.columnDefs.filter(d => d.searchable).map(d => d.name)
            if (this.search.type === 'contains') {
              for (let i in columns) {
                let attr = columns[i]
                let value = _.get(row, attr)
                if (value && value.toString().indexOf(this.search.value) === -1) {
                  return false
                }
              }
            } else if (this.search.type === 'regex') {
              let reg = new RegExp(this.search.value)
              for (let i in columns) {
                let attr = columns[i]
                let value = _.get(row, attr)
                if (value && !reg.test(value.toString())) {
                  return false
                }
              }
            }
          }
        }
        return true
      })
    },
    recordsTotal: function () {
      return this.data.length
    },
    recordsFiltered: function () {
      return this.filteredData.length
    },
    pagination: function () {
      let totalPage = null
      if (this.recordsFiltered !== null) {
        totalPage = Math.ceil(this.recordsFiltered / this.limit)
      }
      return {
        firstPage: 1,
        lastPage: totalPage,
        currentPage: (this.offset / this.limit) + 1
      }
    },
    columnDefs: function () {
      return this.columns.map(function (column) {
        let defult = {
          name: '',
          title: '',
          orderable: true,
          searchable: true,
          visiable: true,
          componentable: false,
          placeHolder: '<null>',
          emptyAsNull: false,
          rawHtml: false,
          search_type: 'contains',
          classes: '',
          data: ''
        }
        if (typeof column === 'string') {
          let t = column
          column = {
            name: t,
            title: t
          }
        }
        if (!column.title) {
          column.title = column.name
        }
        let def = Object.assign(defult, column)
        if (!def.name) {
          def.searchable = false
        }
        return def
      })
    },
    rowData: function () {
      if (this.filteredData.length === 0) {
        return []
      }
      this.orderBy.forEach((o) => {
        let column = this.columnDefs[o[0]].name
        this.filteredData.sort((a, b) => {
          let m = _.get(a, column) > _.get(b, column) ? 1 : -1
          return o[1] === 'asc' ? m : -m
        })
      })
      return this.filteredData.slice(this.offset, this.offset + this.limit)
    },
    rows: function () {
      return this.rowData.map((row, index) => {
        if (typeof (this.trClass) === 'function') {
          row.classes = this.trClass(row, index)
        } else {
          row.classes = this.trClass || []
        }
        let newRow = this.columnDefs.map((def) => {
          let td = {}
          td.classes = def.classes || []
          if (def.componentable) {
            td.component = def.data.call(row, row, this.rowData, this)
          } else {
            if (def.data) {
              if (typeof (def.data) === 'function') {
                td.text = def.data.call(row, row, this.rowData, this)
              } else {
                td.text = def.data
              }
            } else {
              td.text = _.get(row, def.name)
            }
            if (td.text === null || (def.emptyAsNull && td.text === '')) {
              td.rawHtml = false
              td.classes.push('null-value')
              td.text = def.placeHolder
            }
          }
          td.def = def
          return td
        })
        return newRow
      })
    }
  }
}
</script>
<style lang="scss" scoped>
.datatable-container {
  display: block;
  padding: 10px 0px;
  width: 100%;
  position: relative;
  table {
    margin-bottom: 0;
  }
  td.null-value {
    font-weight: bold;
  }
  th {
    pointer-events: none;
  }
  th.searchable {
    i {
      font-size: 0.1em;
      color: skyblue;
    }
  }
  th.orderable {
    pointer-events: auto;
    cursor: pointer;
    background-repeat: no-repeat;
    background-position: center right;
  }
  th.orderable.both {
    background-image: url("");
  }
  th.orderable.asc {
    background-image: url("");
  }
  th.orderable.desc {
    background-image: url("");
  }
  .size-selector {
    float: right;
    margin: 0.5em;
  }
  .table_info {
    float: left;
    clear: both;
    padding-top: 0.55em;
    padding-left: 0.25em;
  }
  .paginate {
    float: right;
    text-align: right;
    padding-top: 0.25em;
    padding-right: 0.25em;
  }
  .clear {
    clear: both
  }
}
</style>
